package cn.always.xiajia.framework.security.core.filter;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.MediaType;
import org.springframework.web.filter.OncePerRequestFilter;

import cn.always.xiajia.admin.client.api.oauth2.OAuth2AccessTokenApi;
import cn.always.xiajia.admin.client.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import cn.always.xiajia.framework.common.config.RpcProperties;
import cn.always.xiajia.framework.common.config.SecurityProperties;
import cn.always.xiajia.framework.common.entity.AjaxResult;
import cn.always.xiajia.framework.common.exception.ServiceException;
import cn.always.xiajia.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.always.xiajia.framework.common.util.ExtServletUtil;
import cn.always.xiajia.framework.common.util.ExtStringUtil;
import cn.always.xiajia.framework.security.core.entity.LoginUser;
import cn.always.xiajia.framework.security.core.util.SecurityFrameworkUtils;
import cn.always.xiajia.framework.web.core.handler.GlobalExceptionHandler;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;

/**
 * Token 过滤器，验证 token 的有效性 验证通过后，获得 {@link LoginUser} 信息，并加入到 Spring Security
 * 上下文
 *
 * @author 芋道源码
 */
@RequiredArgsConstructor
public class TokenAuthenticationFilter extends OncePerRequestFilter {

	private final SecurityProperties securityProperties;

	private final RpcProperties rpcProperties;

	private final GlobalExceptionHandler globalExceptionHandler;

	private final OAuth2AccessTokenApi oAuth2AccessTokenApi;

	@Override
	@SuppressWarnings("NullableProblems")
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws ServletException, IOException {

		// 判断是否外部RPC访问
		if (isRpcUrl(request)) {
			String rpcHeader = request.getHeader(rpcProperties.getRpcHeader());
			if (ExtStringUtil.isBlank(rpcHeader) || rpcHeader.equals(rpcProperties.getRpcHeader())
					|| !rpcHeader.equals(Base64.encode(rpcProperties.getRpcHeaderSecret().getBytes()))) {
				// 必须使用 APPLICATION_JSON_UTF8_VALUE，否则会乱码
				AjaxResult ajaxResult = AjaxResult.error(GlobalErrorCodeConstants.FORBIDDEN.getCode(), "RPC服务无权访问");
				ExtServletUtil.write(response, JSONUtil.toJsonStr(ajaxResult), MediaType.APPLICATION_JSON_UTF8_VALUE);
				return;
			}
		}

		String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader(),
				securityProperties.getTokenParameter());
		if (StrUtil.isNotEmpty(token)) {
			try {
				// 1.1 基于 token 构建登录用户
				LoginUser loginUser = buildLoginUserByToken(token);
				// 1.2 模拟 Login 功能，方便日常开发调试
				if (loginUser == null) {
					loginUser = mockLoginUser(token);
				}

				// 2. 设置当前用户
				if (loginUser != null) {
					SecurityFrameworkUtils.setLoginUser(loginUser, request);
				}
			} catch (Throwable ex) {
				// 必须使用 APPLICATION_JSON_UTF8_VALUE，否则会乱码
				AjaxResult ajaxResult = globalExceptionHandler.allExceptionHandler(request, ex);
				ExtServletUtil.write(response, JSONUtil.toJsonStr(ajaxResult), MediaType.APPLICATION_JSON_UTF8_VALUE);
				return;
			}
		}

		// 继续过滤链
		chain.doFilter(request, response);
	}

	/**
	 * 判断是否外部RPC访问
	 * 
	 * @param request
	 * @return
	 */
	private boolean isRpcUrl(HttpServletRequest request) {
		if (request == null) {
			return false;
		}
		if (request.getRequestURL() == null) {
			return false;
		}
		String url = request.getRequestURL().toString();
		if (ExtStringUtil.isBlank(url)) {
			return false;
		}

		return ExtStringUtil.containsAnyIgnoreCase(url, rpcProperties.getRpcurlprefix());
	}

	/**
	 * 校验token
	 * 
	 * @param token
	 * @return
	 */
	private LoginUser buildLoginUserByToken(String token) {
		try {
			OAuth2AccessTokenCheckRespDTO accessToken = oAuth2AccessTokenApi.checkAccessToken(token);
			if (accessToken == null) {
				return null;
			}

			// 构建登录用户
			return new LoginUser().setUserId(accessToken.getUserId()).setUserCode(accessToken.getUserCode())
					.setUserName(accessToken.getUserName()).setUserType(accessToken.getUserType()).setScopes(accessToken.getScopes());
		} catch (ServiceException serviceException) {
			// 校验 Token 不通过时，考虑到一些接口是无需登录的，所以直接返回 null 即可
			return null;
		}
	}

	/**
	 * 模拟登录用户，方便日常开发调试
	 *
	 * 注意，在线上环境下，一定要关闭该功能！！！
	 * 
	 * @param token MockSecret开头 test-99999-test-test-2
	 * @return
	 */
	private LoginUser mockLoginUser(String token) {
		if (!securityProperties.getMockEnable()) {
			return null;
		}
		// 必须以 mockSecret 开头
		if (!token.startsWith(securityProperties.getMockSecret())) {
			return null;
		}
		// test-99999-test-test-2
		String[] strings = token.split("-");
		if (strings.length < 5) {
			return null;
		}
		// 构建模拟用户
		return new LoginUser().setUserId(Long.valueOf(strings[1])).setUserCode(strings[2]).setUserName(strings[3]).setUserType(strings[4]);
	}

}
